type Configration = {
  startTag?: string;
  endTag?: string;
};

export default class TemplateEngine {
  public startTag: string = '<%';
  public endTag: string = '%>';

  public regexp: RegExp;
  public compiled = ['var tpl = "";'];

  public constructor(config: Configration | null = null) {
    if (config !== null) {
      if (undefined !== config.startTag) {
        this.startTag = config.startTag;
      }
      if (undefined !== config.endTag) {
        this.endTag = config.endTag;
      }
    }

    this.regexp = new RegExp(`${this.startTag}([\\s\\S]*?)${this.endTag}`, 'g');
  }

  protected onText(text: string): void {
    const t = 'tpl += "' + text + '";';

    this.compiled.push(t);
  }

  protected onJs(text: string): void {
    this.compiled.push(text);
  }

  protected onJsPlaceholder(text: string): void {
    const t = 'tpl += ' + text + ';';

    this.compiled.push(t);
  }

  protected onEnd(): void {
    this.compiled.push('return tpl;');
  }

  protected processText(text: string): string {
    let str = text.replace(/\r\n|\n/g, '\\n').replace(/"/g, '\\"');
    if (str.startsWith('\\n')) {
      str = str.substring(2);
    }
    return str;
  }

  public compile(html: string): string {
    let parts = null;
    // the index at which to start the match
    let lastIndex = 0;

    while (null !== (parts = this.regexp.exec(html))) {
      // text
      if (parts.index > lastIndex) {
        let text = html.substring(lastIndex, parts.index);
        text = this.processText(text);

        this.onText(text);
      }
      lastIndex = this.regexp.lastIndex;

      const js = parts[1];
      if ('=' === js.charAt(0)) {
        this.onJsPlaceholder(js.substring(1));
      } else {
        this.onJs(js);
      }
    }

    // text
    this.onText(this.processText(html.substring(lastIndex)));
    this.onEnd();

    return this.compiled.join('\n');
  }

  public run(data: any): string {
    return new Function('$template', this.compiled.join('\n'))(data);
  }

  public reset(): void {
    this.compiled = ['var tpl = "";'];
  }

  public setCompiled(str: string): void {
    this.compiled = [str];
  }
}
